if class == nil then require 'class' end

require 'card'
require 'hand'
require 'deck'
require 'util'
require 'enum'

if HandCombination == nil then HandCombination = class({}) end

function HandCombination:GetBestHand( hand )
    if hand:Count() < 5 then
        hand:Clear()
        return hand
    end
    -- 皇家同花顺 同色 10 J Q K A
    if self:IsRoyalFlush( hand ) then return self:GetRoyalFlush( hand ) end
    return self:GetBestHandEfficiently( hand )
end

function HandCombination:GetBestHandEfficiently( hand )
    if hand:Count() < 5 then
        hand:Clear()
        return hand
    end
    local function tprint(s) end
    -- 同花顺 同色 10 J Q K A
    if self:IsStraightFlush( hand ) then tprint("GetStraightFlush") return self:GetStraightFlush( hand ) end
    -- 四条 
    if self:IsFourOfAKind( hand ) then tprint("GetFourOfAKind")  return self:GetFourOfAKind( hand ) end
    -- 满堂彩 三条+一对
    if self:IsFullHouse( hand ) then tprint("GetFullHouse")  return self:GetFullHouse( hand ) end
    -- 清一色 同花
    if self:IsFlush( hand ) then tprint("GetFlush")  return self:GetFlush( hand ) end
    -- 顺子 一条龙
    if self:IsStraight( hand ) then tprint("GetStraight")  return self:GetStraight( hand ) end
    -- 三条
    if self:IsThreeOfAKind( hand ) then tprint("GetThreeOfAKind")  return self:GetThreeOfAKind( hand ) end
    -- 两对
    if self:IsTwoPair( hand ) then tprint("GetTwoPair")  return self:GetTwoPair( hand ) end
    -- 一对
    if self:IsOnePair( hand ) then tprint("GetOnePair")  return self:GetOnePair( hand ) end
    return self:GetHighCard( hand )
end

function HandCombination:IsRoyalFlush( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local simplifiedhand1, simplifiedhand2
    for i = 1, hand:Count() - 1 do
        if( hand:GetCard(i):GetRank() == hand:GetCard(i+1):GetSuit() and
            hand:GetCard(i):GetRank() == hand:GetCard(i+1):GetSuit() ) then
            simplifiedhand1 = Hand( hand )
            simplifiedhand1:Remove( i )
            simplifiedhand2 = Hand( hand )
            simplifiedhand2:Remove( i + 1 )
            if HandCombination:IsRoyalFlush( simplifiedhand1 ) then
                return true
            end
            if HandCombination:IsRoyalFlush( simplifiedhand2 ) then 
                return true
            end
        end
    end
    local cs = hand:GetCard(1):GetSuit()
    if hand:GetCard(1):GetRank() == 14 and
        hand:GetCard(2):GetRank() == 13 and
        hand:GetCard(3):GetRank() == 12 and
        hand:GetCard(4):GetRank() == 11 and
        hand:GetCard(5):GetRank() == 10 and
        hand:GetCard(2):GetSuit() == cs and
        hand:GetCard(3):GetSuit() == cs and
        hand:GetCard(4):GetSuit() == cs and
        hand:GetCard(5):GetSuit() == cs then
        return true
    end
    return false
end

function HandCombination:GetRoyalFlush( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local straightflush = Hand(self:GetStraightFlush(hand))
    straightflush:SetValue(HAND_VALUE.ROYAL_STRAIGHT)
    if ( straightflush:GetCard(1):GetRank() == 14 ) then
        return straightflush
    else
        straightflush:Clear()
        return straightflush
    end
end
-- 同花顺（Straight Flush）：同一花色，顺序的牌。
function HandCombination:IsStraightFlush( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local simplifiedhand1, simplifiedhand2
    for i = 1, hand:Count() - 2 do
        local c = hand.myHand[i]
        local c1 = hand.myHand[i + 1]
        if (c:GetRank() == c1:GetRank() and c:GetSuit() == c1:GetSuit()) then
            simplifiedhand1 = Hand( hand )
            simplifiedhand1:Remove( i )
            simplifiedhand2 = Hand( hand )
            simplifiedhand2:Remove( i + 1 )
            if HandCombination:IsStraightFlush( simplifiedhand1 ) then
                return true
            end
            if HandCombination:IsStraightFlush( simplifiedhand2 ) then
                return true
            end
        end
    end

    for i = 1, hand:Count() - 4 do
        local cr = hand:GetCard(i):GetRank()
        local cs = hand:GetCard(i):GetSuit()
        if (
            cr == 5 and
            4 == hand:GetCard(i+1):GetRank() and 
            3 == hand:GetCard(i+2):GetRank() and 
            2 == hand:GetCard(i+3):GetRank() and 
            14 == hand:GetCard(i+4):GetRank() and
            cs == hand:GetCard(i+1):GetSuit() and 
            cs == hand:GetCard(i+2):GetSuit() and 
            cs == hand:GetCard(i+3):GetSuit() and 
            cs == hand:GetCard(i+4):GetSuit()
            ) then
            return true
        end
    end
    for i = 1, hand:Count() - 4 do
        local cr = hand:GetCard(i):GetRank()
        local cs = hand:GetCard(i):GetSuit()
        if (
            cr == hand:GetCard(i+1):GetRank() + 1 and 
            cr == hand:GetCard(i+2):GetRank() + 2 and 
            cr == hand:GetCard(i+3):GetRank() + 3 and 
            cr == hand:GetCard(i+4):GetRank() + 4 and 
            cs == hand:GetCard(i+1):GetSuit() and 
            cs == hand:GetCard(i+2):GetSuit() and 
            cs == hand:GetCard(i+3):GetSuit() and 
            cs == hand:GetCard(i+4):GetSuit()
            ) then
            return true
        end
    end
    return false
end

function HandCombination:GetStraightFlush( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local straightflush = Hand()
    straightflush:SetValue(HAND_VALUE.STRAIGHT_FLUSH)
    if ( hand:GetCard(1):GetRank() == 14) then
        hand:Add( Card( RANK.ACE, hand:GetCard(1):GetSuit()))
    end
    straightflush:Add(hand:GetCard(1))
    local ptr1, ptr2 = 1, 2
    while( ptr1 <= hand:Count() - 1 or ptr2 <= hand:Count()) do
        if straightflush:Count() >= 5 then
            break
        end
        local rank1, rank2 = hand:GetCard(ptr1):GetRank(), hand:GetCard(ptr2):GetRank()
        local suit1, suit2 = hand:GetCard(ptr1):GetSuit(), hand:GetCard(ptr2):GetSuit()
        if ( rank1 - rank2 == 1 and suit1 == suit2) then
            straightflush:Add(hand:GetCard(ptr2))
            ptr1 = ptr2
            ptr2 = ptr2 + 1
        elseif (rank1 == 2 and rank2 == 14 and suit1 == suit2) then
            straightflush:Add(hand:GetCard(ptr2))
            ptr1 = ptr2
            ptr2 = ptr2 + 1
        else
            if rank1 - rank2 <= 1 then
                ptr2 = ptr2 + 1
            else
                straightflush:Clear()
                straightflush:SetValue(HAND_VALUE.STRAIGHT_FLUSH)
                ptr1 = ptr1 + 1
                ptr2 = ptr1 + 1
                straightflush:Add( hand:GetCard(ptr1))
            end
        end
    end
    if hand:GetCard(1):GetRank() == 14 then
        hand:Remove( hand:Count() - 1)
    end
    straightflush:SetHighRank(straightflush:GetCard(1):GetRank())
    if straightflush:Count() < 5 then
        straightflush:Clear()
    end
    return straightflush
end
-- 四条（Four of a Kind，亦称“铁支”、“四张”或“炸弹”）：有四张同一点数的牌。
function HandCombination:IsFourOfAKind( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    for i = 1, hand:Count() - 3 do
        if hand:GetCard(i):GetRank() == hand:GetCard(i+1):GetRank() and hand:GetCard(i):GetRank() == hand:GetCard(i+2):GetRank() and hand:GetCard(i):GetRank() == hand:GetCard(i+3):GetRank() then
            return true
        end
    end
    return false
end

function HandCombination:GetFourOfAKind( ohand )
    local hand = Hand(ohand)
    local fourofakind = Hand()
    fourofakind:SetValue(HAND_VALUE.FOUR_OF_A_KIND)
    hand:SortByRank()
    for i = 1, hand:Count() - 3 do
        if hand:GetRankAt(i) == hand:GetRankAt(i+1) and
            hand:GetRankAt(i) == hand:GetRankAt(i+2) and 
            hand:GetRankAt(i) == hand:GetRankAt(i+3) then
            
            fourofakind:Add(hand:GetCard(i))
            fourofakind:Add(hand:GetCard(i+1))
            fourofakind:Add(hand:GetCard(i+2))
            fourofakind:Add(hand:GetCard(i+3))
            fourofakind:SetHighRank(hand:GetCard(i):GetRank())
            break
        end
    end
    return fourofakind
end
-- 满堂红（Fullhouse，亦称“俘虏”、“骷髅”、“夫佬”、“葫芦”）：三张同一点数的牌，加一对其他点数的牌。
function HandCombination:IsFullHouse( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local threeofakind,pair = false, false
    local threeofakindRank = 0
    for i = 1, hand:Count() - 2 do
        if hand:GetCard(i):GetRank() == hand:GetCard(i+1):GetRank() and hand:GetCard(i):GetRank() == hand:GetCard(i+2):GetRank() then
            threeofakind = true
            threeofakindRank = hand:GetCard(i):GetRank()
            break
        end
    end
    for i = 1, hand:Count() - 1 do
        if hand:GetCard(i):GetRank() == hand:GetCard(i+1):GetRank() and hand:GetCard(i):GetRank() ~= threeofakindRank then
            pair = true

            break
        end
    end
    if threeofakind == true and pair == true then 
        return true
    else
        return false
    end
end

function HandCombination:GetFullHouse( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local fullhouse = Hand()
    fullhouse:SetValue(HAND_VALUE.FULL_HOUSE)
    local threeofakind, pair = false
    local threeofakindRank = 0
    for i = 1, hand:Count() - 2 do
        if hand:GetCard(i):GetRank() == hand:GetCard(i+1):GetRank() and hand:GetCard(i):GetRank() == hand:GetCard(i+2):GetRank() then
            threeofakind = true
            threeofakindRank = hand:GetCard(i):GetRank()
            fullhouse:Add( hand:GetCard(i) )
            fullhouse:Add( hand:GetCard(i+1) )
            fullhouse:Add( hand:GetCard(i+2) )
            fullhouse:SetHighRank( hand:GetCard(i):GetRank() )
            break
        end
    end
    for i = 1, hand:Count() - 1 do
        if hand:GetCard(i):GetRank() == hand:GetCard(i+1):GetRank() and hand:GetCard(i):GetRank() ~= threeofakindRank then
            pair = true
            fullhouse:Add(hand:GetCard(i))
            fullhouse:Add(hand:GetCard(i+1))
            fullhouse:SetHighRank(hand:GetCard(i):GetRank())
            break
        end
    end
    if threeofakind == true and pair == true then 
        return fullhouse
    else
        fullhouse:Clear()
        return fullhouse
    end
end
-- 同花（Flush，简称“花”：五张同一花色的牌。
function HandCombination:IsFlush( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local dc, cc, hc, sc = 0,0,0,0
    for i = 1, hand:Count() do
        if hand:GetCard(i):GetSuit() ==     SUIT.DIAMONDS   then dc = dc + 1
        elseif hand:GetCard(i):GetSuit() == SUIT.CLUBS      then cc = cc + 1
        elseif hand:GetCard(i):GetSuit() == SUIT.HEARTS     then hc = hc + 1
        elseif hand:GetCard(i):GetSuit() == SUIT.SPADES     then sc = sc + 1
        end
    end
    if dc >= 5 then return true end
    if cc >= 5 then return true end
    if hc >= 5 then return true end
    if sc >= 5 then return true end
    return false
end

function HandCombination:GetFlush( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local flush = Hand()
    local dc, cc, hc, sc = 0,0,0,0
    for i = 1, hand:Count() do
        if hand:GetCard(i):GetSuit() ==     SUIT.DIAMONDS   then dc = dc + 1
        elseif hand:GetCard(i):GetSuit() == SUIT.CLUBS      then cc = cc + 1
        elseif hand:GetCard(i):GetSuit() == SUIT.HEARTS     then hc = hc + 1
        elseif hand:GetCard(i):GetSuit() == SUIT.SPADES     then sc = sc + 1
        end
    end
    local function _getflush(suit)
        local ret = Hand()
        ret:SetValue(HAND_VALUE.FLUSH)
        for _i = 1, hand:Count() do
            if hand:GetCard(_i):GetSuit() == suit then
                ret:Add(hand:GetCard(_i))
                ret:SetHighRank(hand:GetCard(_i):GetRank())
            end
            if ret:Count() >= 5 then
                break
            end
        end
        return ret
    end
    if dc >= 5 then  return _getflush(SUIT.DIAMONDS) end
    if cc >= 5 then  return _getflush(SUIT.CLUBS) end
    if hc >= 5 then  return _getflush(SUIT.HEARTS) end
    if sc >= 5 then  return _getflush(SUIT.SPADES) end
end

-- 顺子（Straight，亦称“蛇”）：五张顺连的牌。
function HandCombination:IsStraight( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    if(hand:GetCard(1):GetRank()==14) then
        hand:Add( Card(RANK.ACE,hand:GetCard(1):GetSuit()))
    end
    local straightCount=1
    for i = 1, hand:Count() - 1 do
        if (straightCount == 5) then
            break
        end
        local currentrank = hand:GetCard(i):GetRank()
        if (currentrank - hand:GetCard(i + 1):GetRank() == 1) then
            straightCount = straightCount + 1
        elseif (currentrank == 2 and hand:GetCard(i + 1):GetRank() == 14) then
            straightCount = straightCount + 1
        elseif (currentrank - hand:GetCard(i + 1):GetRank() > 1) then
            straightCount = 1
        end
    end
    if (hand:GetCard(1):GetRank() == 14) then
        hand:Remove(hand:Count() - 1)
    end
    if (straightCount == 5) then
        return true
    end
    return false
end

function HandCombination:GetStraight( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local straight = Hand()
    straight:SetValue(HAND_VALUE.STRAIGHT)
    if (hand:GetCard(1):GetRank() == 14) then
        hand:Add( Card(RANK.ACE, hand:GetCard(1):GetSuit()))
    end
    local straightCount = 1
    straight:Add(hand:GetCard(2))
    for i = 1, hand:Count() - 1 do
        if (straightCount == 5) then
            break
        end
        local currentrank = hand:GetCard(i):GetRank()
        if (currentrank - hand:GetCard(i + 1):GetRank() == 1) then
            straightCount = straightCount + 1
            straight:Add(hand:GetCard(i+1))
        elseif (currentrank == 2 and hand:GetCard(i + 1):GetRank() == 14) then
            straightCount = straightCount + 1
            straight:Add(hand:GetCard(i+1))
        elseif (currentrank - hand:GetCard(i + 1):GetRank() > 1) then
            straightCount = 1
            straight:Clear()
            straight:SetValue(HAND_VALUE.STRAIGHT)
            straight:Add(hand:GetCard(i+1))
        end
    end
    if (hand:GetCard(1):GetRank() == 14) then
        hand:Remove(hand:Count() - 1)
    end
    -- if (straightCount ~= 5) then
    --     straight:Clear()
    -- end
    straight:SetHighRank(straight:GetCard(1):GetRank())
    return straight
end

-- 三条（Three of a kind，亦称“三张”）：有三张同一点数的牌。
function HandCombination:IsThreeOfAKind( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    for i = 1, hand:Count() - 2 do
        if (hand:GetCard(i):GetRank() == hand:GetCard(i + 1):GetRank()
            and hand:GetCard(i):GetRank() == hand:GetCard(i + 2):GetRank()) then
            return true
        end
    end
    return false
end

function HandCombination:GetThreeOfAKind( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local threeofakind = Hand()
    threeofakind:SetValue(HAND_VALUE.THREE_OF_A_KIND)
    for i = 1, hand:Count() - 2 do
        if (hand:GetCard(i):GetRank() == hand:GetCard(i + 1):GetRank()
            and hand:GetCard(i):GetRank() == hand:GetCard(i + 2):GetRank()) then
            threeofakind:SetHighRank(hand:GetCard(i):GetRank())
            threeofakind:Add(hand:GetCard(i))
            threeofakind:Add(hand:GetCard(i + 1))
            threeofakind:Add(hand:GetCard(i + 2))
            break
        end
    end
    return self:GetKickers(hand, threeofakind)
end

-- 两对（Two Pairs）：两张相同点数的牌，加另外两张相同点数的牌。
function HandCombination:IsTwoPair( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local pairCount = 0
    for i = 1, hand:Count() - 1 do
        if hand:GetCard(i):GetRank() == hand:GetCard(i + 1):GetRank() then
            pairCount = pairCount + 1
            i = i + 1
        end
    end
    if (pairCount >= 2) then
        return true
    else
        return false
    end
end

function HandCombination:GetTwoPair( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local twopair = Hand()
    twopair:SetValue(HAND_VALUE.TWO_PAIR)
    local pairCount = 0
    for i = 1, hand:Count() - 1 do
        if hand:GetCard(i):GetRank() == hand:GetCard(i + 1):GetRank() then
            twopair:SetHighRank(hand:GetCard(i):GetRank())
            twopair:Add(hand:GetCard(i))
            twopair:Add(hand:GetCard(i+1))
            pairCount = pairCount + 1
            if (pairCount == 2) then
                break
            end
            i = i + 1
        end
    end
    if (pairCount == 2) then
        return self:GetKickers(hand,twopair)
    else
        twopair:Clear()
        return twopair
    end
end

-- 一对（One Pair）：两张相同点数的牌。
function HandCombination:IsOnePair( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    for i = 1, hand:Count() - 1 do
        if (hand:GetCard(i):GetRank() == hand:GetCard(i + 1):GetRank()) then
            return true
        end
    end
    return false
end

function HandCombination:GetOnePair( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local onepair = Hand()
    onepair:SetValue(HAND_VALUE.ONE_PAIR)
    for i = 1, hand:Count() - 1 do
        if (hand:GetCard(i):GetRank() == hand:GetCard(i + 1):GetRank()) then
            onepair:SetHighRank(hand:GetCard(i):GetRank())
            onepair:Add(hand:GetCard(i))
            onepair:Add(hand:GetCard(i + 1))
            break
        end
    end
    return self:GetKickers(hand, onepair)
end

function HandCombination:IsHighCard( ohand )
    local hand = Hand(ohand)
    return true
end

function HandCombination:GetHighCard( ohand )
    local hand = Hand(ohand)
    hand:SortByRank()
    local highcard = Hand()
    highcard:SetValue(HAND_VALUE.HIGH)
    highcard:Add(hand:GetCard(1))
    highcard:SetHighRank(hand:GetCard(1):GetRank())
    return self:GetKickers(hand, highcard)
end

function HandCombination:GetKickers( hand, specialCards )
    local hand = Hand(ohand)
    if (specialCards:Count() == 0) then
        return specialCards
    end
    for i = 1, specialCards:Count() do
        hand:Remove(specialCards:GetCard(i))
    end
    for i = 1, hand:Count() do
        if (specialCards:Count() >= 5) then
            break
        end
        specialCards:Add(hand:GetCard(i))
        specialCards:SetValue(hand:GetCard(i):GetRank())
    end
    return specialCards
end

---[[
local values = {}
local count = 0
while(true) do
    count = count + 1
    local deck = Deck()
    deck:Shuffle()

    local hand = Hand()
    for i = 1,7 do
        local card = deck:Deal(true)
        hand:Add( card )
    end
    local h
    local stats, err = pcall(function()
        h = HandCombination:GetBestHand( hand )
    end)
    if not stats then
        -- print ( debug.traceback(error(err, 2)))
    else
        -- if values[h:GetValue()] == nil then
            print(hand:RawToString(), "  ", h:RawToString(), "  ",h:ToString())
        -- end
        values[h:GetValue()] = values[h:GetValue()] or 0
        values[h:GetValue()] = values[h:GetValue()] + 1
        if h:GetValue() == 10 then
            break
        end
    end
end
print("total -> ", count)
for k,v in pairs(values) do
    print(k,v, v/10000)
end
--]]
--[[
local hand = Hand()
hand:Add(Card(2, 1, true))
hand:Add(Card(3, 2, true))
hand:Add(Card(4, 3, true))
hand:Add(Card(5, 4, true))
hand:Add(Card(14, 1, true))
local h = HandCombination:GetBestHand( hand )
print(h:ToString(), h:RawToString())
--]]